﻿using System.Collections.Generic;
using SlimDX.D3DCompiler;
using SlimDX.Direct3D11;

namespace JamesZhao.Engine.Graphics.Effects
{
    public class GpuProgram
    {
        protected const string ShaderPath = "Shaders/";

        public const string WorldViewProjectionVarKey = "WorldViewProjection";
        public const string WorldViewVarKey = "WorldView";
        public const string ViewVarKey = "View";
        public const string InverseProjectionVarKey = "InverseProjection";
        public IDictionary<string, EffectVariable> Parameters;

        internal GpuProgram(GraphicsDevice device, string effectFile)
        {
            ShaderBytecode shader = ShaderBytecode.CompileFromFile(effectFile, "fx_5_0", ShaderFlags.Debug,
                                                                   EffectFlags.None);
            Effect = new Effect(device.Device, shader);
            EffectTechnique technique = Effect.GetTechniqueByIndex(0);
            EffectPass pass = technique.GetPassByIndex(0);

            InputLayout = new InputLayout(device.Device, pass.Description.Signature, BufferConstants.InputLayoutElements);

            LoadVariables();

            WorldViewProjectionVar = MatrixVariables[WorldViewProjectionVarKey];
            WorldViewVar = MatrixVariables[WorldViewVarKey];
            ViewVar = MatrixVariables[ViewVarKey];
            InverseProjectionVar = MatrixVariables[InverseProjectionVarKey];
        }

        public IDictionary<string, EffectMatrixVariable> MatrixVariables { get; private set; }
        public IDictionary<string, EffectVectorVariable> VectorVariables { get; private set; }
        public IDictionary<string, EffectScalarVariable> ScalarVariables { get; private set; }
        public IDictionary<string, EffectResourceVariable> ResourceVariables { get; private set; }

        public Effect Effect { get; set; }
        public EffectMatrixVariable WorldViewProjectionVar { get; private set; }
        public EffectMatrixVariable WorldViewVar { get; private set; }
        public EffectMatrixVariable ViewVar { get; private set; }
        public EffectMatrixVariable InverseProjectionVar { get; private set; }
        public InputLayout InputLayout { get; private set; }

        private void LoadVariables()
        {
            MatrixVariables = new Dictionary<string, EffectMatrixVariable>();
            VectorVariables = new Dictionary<string, EffectVectorVariable>();
            ScalarVariables = new Dictionary<string, EffectScalarVariable>();
            ResourceVariables = new Dictionary<string, EffectResourceVariable>();

            int numOfVars = Effect.Description.GlobalVariableCount;
            for (int i = 0; i < numOfVars; i++)
            {
                EffectVariable effectVariable = Effect.GetVariableByIndex(i);
                string effectName = effectVariable.Description.Name;
                switch (effectVariable.GetVariableType().Description.TypeName)
                {
                    case "float4x4":
                        MatrixVariables[effectName] = effectVariable.AsMatrix();
                        break;
                    case "float":
                    case "int":
                        ScalarVariables[effectName] = effectVariable.AsScalar();
                        break;
                    case "int2":
                    case "float4":
                        VectorVariables[effectName] = effectVariable.AsVector();
                        break;
                    case "Texture2D":
                        ResourceVariables[effectName] = effectVariable.AsResource();
                        break;
                }
            }
        }

        public void ApplyInputLayout(GraphicsDevice device)
        {
            device.ImmediateContext.InputAssembler.InputLayout = InputLayout;
        }
    }
}